/**
 * apply virtual dom to real dom
 */
;(function() {
  // 虚拟dom
  function vnode(tag, data, children, text, elm) {
    this.tag = tag
    this.data = data
    this.children = children
    this.text = text
    this.elm = elm
  }

  function normalizeChildren(children) {
    if (typeof children === 'string') {
      return [createTextVNode(children)]
    }
    return children
  }

  function createTextVNode(val) {
    return new vnode(undefined, undefined, undefined, String(val))
  }
  // 创建虚拟dom
  function createElement(tag, data, children) {
    return new vnode(tag, data, normalizeChildren(children), undefined, undefined)
  }
  // 创建真实dom
  function createElm(vnode) {
    var tag = vnode.tag
    var data = vnode.data
    var children = vnode.children

    if (tag !== undefined) {
      vnode.elm = document.createElement(tag)

      // 有标签属性设置属性
      if (data.attrs !== undefined) {
        var attrs = data.attrs
        for (var k in attrs) {
          vnode.elm.setAttribute(k, attrs[k])
        }
      }
      // 有嵌套子节点，嵌套子节点
      if (children) {
        createChildren(vnode, children)
      }
    } else {
      // 非标签元素，创建文本标签
      vnode.elm = document.createTextNode(vnode.text)
    }
    return vnode.elm
  }

  // 递归创建嵌套子节点
  function createChildren(vnode, children) {
    for (var i = 0; i < children.length; i++) {
      vnode.elm.appendChild(createElm(children[i]))
    }
  }

  // 初始化响应式数据并设置代理
  function initData(vm) {
    var data = (vm.$data = vm.$options.data)
    Object.keys(data).forEach(function(key) {
      proxy(vm, key)
    })
  }

  function proxy(vm, key) {
    Object.defineProperty(vm, key, {
      configurable: true,
      enumerable: true,
      get: function() {
        return vm.$data[key]
      },
      set: function(value) {
        vm.$data[key] = value
      },
    })
  }

  function Vue(options) {
    this.$options = options

    initData(this)
    this.mount(document.querySelector(options.el))
  }

  Vue.prototype.mount = function(el) {
    this.$el = el
    var vnode = this.$options.render.call(this)
    this.patch(this.$el, vnode)
  }

  // 更新虚拟dom到真实dom
  Vue.prototype.patch = function(oldVnode, vnode) {
    var isRealElement = oldVnode.nodeType !== undefined // 虚拟 dom 没有 nodeType 属性

    if (isRealElement) {
      createElm(vnode)
      var parent = oldVnode.parentNode
      parent.insertBefore(vnode.elm, oldVnode)
      parent.removeChild(oldVnode)
    }
    return vnode.elm
  }
  new Vue({
    el: '#app',
    data: {
      message: 'Hello world',
    },
    render() {
      return createElement(
        'div',
        {
          attrs: {
            class: 'wrapper',
          },
        },
        [
          createElement(
            'p',
            {
              attrs: {
                class: 'inner',
              },
            },
            this.message
          ),
        ]
      )
    },
  })
})()
